package com.zx.handler;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.zx.model.ConfContent;
import com.zx.model.log.LogConfContent;
import com.zx.model.log.LogMode;
import com.zx.util.Assert;
import com.zx.util.ConfigNodeUtil;
import com.zx.util.NgnixDateUtil;
import com.zx.util.PrintlnUtil;

/**
 * @Project: ngnix_analyze
 * @Title: LogFormatHandler
 * @Description: 日志解析类，支持自定义的日志，复杂类型的解析
 * @author: xue.zhang
 * @date: 2018年8月1日上午9:56:06
 * @company: alibaba
 * @Copyright: Copyright (c) 2017
 * @version v1.0
 */
public class LogFormatHandler {

	//private final static Logger log = Logger.getLogger(LogFormatHandler.class.toString());
	
	private List<ConfContent> logFormatConfigs;

	private List<ConfContent> logAccessConfigs;
	
	private Map<String, LogConfContent> parseRuleMap;
	
	/** 日志总配置的父级节点名称 */
	@SuppressWarnings("unused")
	private final static String LOG_FORMAT_PARENT_NODE 	= "http";
	
	/** 日志总配置的节点名称 */
	private final static String LOG_FORMAT_NODE		 	= "log_format";

	/** 日志对应服务点名称 */
	private final static String LOG_ACCESS_NODE		 	= "access_log";
	
	/** 日志的时间格式类型编号 com.zx.util.NgnixDateUtil */
	private int logTimeDateFormatIndex = 0;

	/** 日志定义的内容规则 */
	private final static Map<String, String> LOG_RULE_ARR = new HashMap<>();
	static {
		LOG_RULE_ARR.put("$remote_addr", "$remote_addr");//客户端地址
		LOG_RULE_ARR.put("$http_x_forwarded_for", "$http_x_forwarded_for");
		LOG_RULE_ARR.put("$remote_user", "$remote_user");//客户端用户名称
		LOG_RULE_ARR.put("$time_local", "$time_local");//访问时间和时区
		LOG_RULE_ARR.put("$request", "$request");//请求的URI和HTTP协议
		LOG_RULE_ARR.put("$status", "$status");//HTTP请求状态
		LOG_RULE_ARR.put("$http_referer", "$http_referer");//url跳转来源
		LOG_RULE_ARR.put("$http_user_agent", "$http_user_agent");//用户终端浏览器等信息
		LOG_RULE_ARR.put("$body_bytes_sent", "$body_bytes_sent");//发送给客户端文件内容大小
		
		LOG_RULE_ARR.put("$http_host", "$http_host");//请求地址，即浏览器中你输入的地址（IP或域名）
		LOG_RULE_ARR.put("$upstream_status", "$upstream_status");//upstream状态
		LOG_RULE_ARR.put("$ssl_protocol", "$ssl_protocol");//SSL协议版本
		LOG_RULE_ARR.put("$ssl_cipher", "$ssl_cipher");//交换数据中的算法
		LOG_RULE_ARR.put("$upstream_addr", "$upstream_addr");//后台upstream的地址，即真正提供服务的主机地址
		LOG_RULE_ARR.put("$request_time", "$request_time");//整个请求的总时间
		LOG_RULE_ARR.put("$upstream_response_time", "$upstream_response_time");//请求过程中，upstream响应时间
	}
	
	public LogFormatHandler(List<ConfContent> allConfContents) {
		this.logFormatConfigs = new ArrayList<>();
		this.parseRuleMap = new HashMap<String, LogConfContent>();
		this.setConfig(allConfContents);
	}
	
	public int getLogTimeDateFormatIndex() {
		return logTimeDateFormatIndex;
	}

	public void setLogTimeDateFormatIndex(int logTimeDateFormatIndex) {
		this.logTimeDateFormatIndex = logTimeDateFormatIndex;
	}

	public List<LogMode> parseFile(File logFile) {
		if(logFile == null || !logFile.exists()) {
			throw new RuntimeException("日志文件不存在");
		}
		
		List<LogMode> result = new ArrayList<>();
		String fileName = logFile.getName();
		try (
        		FileInputStream fis = new FileInputStream(logFile);
        		InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); 
        		BufferedReader br = new BufferedReader(isr) ;
        		) {
            int index = -1;
            String tmp = null ;
            while((tmp = br.readLine()) != null) {
            	LogMode parseStr = this.parseStr(fileName, tmp);
            	if(index == -1) {
            		index = NgnixDateUtil.getFormatIndex(parseStr.getTimeLocal());
            	}
            	// 解析时间格式
            	Date date = NgnixDateUtil.parse(parseStr.getTimeLocal(), index);
            	parseStr.setTimeLocalDate(date);
            	result.add(parseStr);
            }
        } catch (IOException e) {
            e.printStackTrace() ;
        } 
		
		return result;
	}
	
	/**
	 * 存入log的日志的配置信息
	 * @param allConfContents
	 * @author: xue.zhang
	 * @date 2018年7月31日下午5:45:53
	 */
	public void setConfig(List<ConfContent> allConfContents) {
		if(Assert.isEmpty(allConfContents)) {
			throw new RuntimeException("配置不能为空");
		} 
		// 获得日志相关的配置
		PrintlnUtil.println("开始获得日志相关的配置");
		this.logFormatConfigs = ConfigNodeUtil.getNode(allConfContents, LOG_FORMAT_NODE);
		this.logAccessConfigs =  ConfigNodeUtil.getNode(allConfContents, LOG_ACCESS_NODE);
		if(Assert.isEmpty(logFormatConfigs) || Assert.isEmpty(logAccessConfigs)) {
			throw new RuntimeException("日志配置不能为空");
		} 
		// 初始化日志解析规则
		PrintlnUtil.println("开始初始化日志解析规则");
		this.initParseRule();
		// 校验参数
		PrintlnUtil.println("开始校验参数");
		this.validateLogFormat();
	}
	/**
	 * 初始化规则
	 * @author: xue.zhang
	 * @date 2018年8月1日下午9:04:20
	 */
	private void initParseRule() {
		if(Assert.isEmpty(logFormatConfigs) || Assert.isEmpty(logAccessConfigs)) {
			throw new RuntimeException("日志配置不能为空");
		} 
		
		for (ConfContent accessConfig : this.logAccessConfigs) {
			LogConfContent logConfigMode = new LogConfContent();
			int indexBlank = accessConfig.getConfValue().indexOf(" ");
			String logFilePath = accessConfig.getConfValue().substring(0, indexBlank).trim();
			String logFormatName = accessConfig.getConfValue().substring(indexBlank).trim();
			
			String logFileName = logFilePath.substring(logFilePath.lastIndexOf("/") + 1);
			logConfigMode.setLogFileName(logFileName);
			logConfigMode.setLogFilePath(logFilePath);
			logConfigMode.setLogFormatName(logFormatName);
			
			for (ConfContent logFormatConfig : this.logFormatConfigs) {
				String s = logFormatConfig.getConfValue();
				indexBlank = s.indexOf(" ");
				String logFormatName2 = s.substring(0, indexBlank).trim();
				if(logFormatName2.equals(logFormatName)) {
					String logFormat = s.substring(indexBlank).trim().replace("'", "");
					logConfigMode.setLogFormatContent(logFormat);
					break;
				}
			}
			if(Assert.isEmpty(logConfigMode.getLogFormatContent())) {
				throw new RuntimeException("没有解析到logFormat" + logConfigMode);
			}
			this.parseRuleMap.put(logFileName, logConfigMode);
		}
		
		
	}
	/**
	 * 解析一行日志
	 * @param fileName
	 * @param line
	 * @return
	 * @author: xue.zhang
	 * @date 2018年8月1日上午9:37:43
	 */
	public LogMode parseStr(String fileName, String line) {
		LogConfContent logConfigMode = this.parseRuleMap.get(fileName);
		
		String logFormatContent = logConfigMode.getLogFormatContent();
		
		String tempLogFormatText = logFormatContent.trim();
		String tempString = line.trim();
		
		LogMode logMode = new LogMode();
		
		while(tempLogFormatText.length() > 0 && tempString.length() > 0) {
			// 找到开头
			int paramsBeginIndex = tempLogFormatText.indexOf("$");
			if(paramsBeginIndex == -1 || paramsBeginIndex >= tempString.length()) {
				break;
			}
			
			tempLogFormatText = tempLogFormatText.substring(paramsBeginIndex).trim();
			tempString = tempString.substring(paramsBeginIndex).trim();
			// 找到结束为止的字符
			char fristEndChar = this.getFristEndChar(tempLogFormatText);
			// 获得找到一个值的位置
			int logFormatEndIndex = tempLogFormatText.indexOf(fristEndChar);
			int logStrEndIndex = tempString.indexOf(fristEndChar);
			
			if(logFormatEndIndex == -1 || logStrEndIndex == -1) {
				throw new RuntimeException("解析异常, tempLogFormatText=" + tempLogFormatText +", tempString=" + tempString);
			}
			
			String rule = tempLogFormatText.substring(0, logFormatEndIndex);
			String ruleValue = tempString.substring(0, logStrEndIndex);
			
			this.setProperty(logMode, rule, ruleValue);
			
			tempLogFormatText = tempLogFormatText.substring(logFormatEndIndex).trim();
			tempString = tempString.substring(logStrEndIndex).trim();
		}
		return logMode;
	}
	
	
	/**
	 * @Title: 找到logFormat中第一个参数后面的分隔符是什么
	 * @param logFormatText
	 * @return
	 * @date: 2018年7月31日下午10:49:20
	 */
	private char getFristEndChar(String logFormatText) {
		for (int i = 1; i < logFormatText.length(); i++) {
			char charAt = logFormatText.charAt(i);
			if( (charAt >= 'a' && charAt <= 'z') || charAt == '$' || charAt == '_') {
				continue;
			} else {
				return charAt;
			}
		}
		throw new RuntimeException("日志配置解析异常" + logFormatText);
	} 
	/**
	 * @Title: 根据参数名称封装到对象中
	 * @param log
	 * @param rule
	 * @param ruleValue
	 * @date: 2018年8月1日下午11:48:57
	 */
	private void setProperty(LogMode log, String rule, String ruleValue) {
		switch (rule) {
			case "$remote_addr" ://1
				log.setRemoteAddr(ruleValue);
				break;
			case "$http_x_forwarded_for" ://2
				log.setHttpXForwardedFor(ruleValue);
				break;
			case "$remote_user" ://3
				log.setRemoteUser(ruleValue);
				break;
			case "$time_local" ://4
				log.setTimeLocal(ruleValue);
				break;
			case "$request" ://5
				log.setRequest(ruleValue);
				break;
			case "$status" ://6
				log.setStatus(ruleValue);
				break;
			case "$body_bytes_sent" ://7
				log.setBodyBytesSent(ruleValue);
				break;
			case "$http_referer" ://8
				log.setHttpReferer(ruleValue);
				break;
			case "$http_user_agent" ://9
				log.setHttpUserAgent(ruleValue);
				break;
			case "$http_host" ://10
				log.setHttpHost(ruleValue);
				break;
			case "$upstream_status" ://11
				log.setUpstreamStatus(ruleValue);
				break;
			case "$ssl_protocol" ://12
				log.setSslProtocol(ruleValue);
				break;
			case "$ssl_cipher" ://13
				log.setSslCipher(ruleValue);
				break;
			case "$upstream_addr" ://14
				log.setUpstreamAddr(ruleValue);
				break;
			case "$request_time" ://15
				log.setRequestTime(ruleValue);
				break;
			case "$upstream_response_time" ://16
				log.setUpstreamResponseTime(ruleValue);
				break;
			default :
				break;
		}
	}
	/**
	 * @Title: 校验logFormat是否正确
	 * @date: 2018年8月1日下午11:49:40
	 */
	private void validateLogFormat() {
		Set<Entry<String,LogConfContent>> entrySet = this.parseRuleMap.entrySet();
		for (Entry<String, LogConfContent> entry : entrySet) {
			String logFormatContent = entry.getValue().getLogFormatContent().trim();
			PrintlnUtil.println(String.format("开始校验日志文件[%s],logFormat模板[%s]", entry.getKey(),  logFormatContent));
			String[] sp = logFormatContent.replaceAll("[^a-z_$]", "").split("\\$");
			for (String rule : sp) {
				if(rule.length() != 0 && !LOG_RULE_ARR.containsKey("$" + rule) ) {
					PrintlnUtil.println(String.format("日志文件[%s]解析模板异常, 出现异常的参数类型：[%s]", entry.getKey(),  "$" + rule));
					throw new RuntimeException("出现异常的参数类型：" + rule);
				}
			}
			PrintlnUtil.println(String.format("校验日志文件[%s]logFormat模板成功", entry.getKey()));
		}
	}

	public Map<String, LogConfContent> getParseRuleMap() {
		return parseRuleMap;
	}
	
}
